// 题意：给定一个n个点m条边的简单联通图，边为偶数。问是否可能有一个边的匹配，
//       使得每对边都有一个公共顶点。
//
// 题解：good pro。图论好题。根据神犇思想，基于图的分治。选中一个点，
//       相邻的点所属不同联通块分治处理，利用时间戳处理后向边。否则
//       根据每个点是否匹配完来做。具体过程参考代码，以下是神犇伪代码：
//
//			 dfs u
//			 记录u的时间戳
//			 px=-1//记录用于“调剂”的那个边
//			 对于u的每一个邻居y
//				如果y还没有访问，dfs y，
//					如果返回多余一条边(y,w),则输出(u,y) (y,w)
//					否则若px不是-1
//						px=y
//					否则输出(px,u) (u,y)并px=-1
//				如果y访问过，但是它的时间戳比u晚，则
//					若px不是-1
//						px=y
//					否则输出(px,u) (u,y)并px=-1
//			  最后返回px
//       
//       另，无解是个什么图，手画的时候还没怎么画出来。
//
// run: $exec < input
// opt: 0
// flag: -g
#include <cstdio>

struct pair_edge
{
	int x, y, z;
};

int const maxn = 20100;
int const maxm = 100100;
int end_point[2 * maxm], next[2 * maxm], head[maxn];
int alloc = 1;

pair_edge ans[maxm];
int time[maxn];
bool vis[maxn];
int n, m, tot, tick;

void add_edge(int u, int v)
{
	end_point[alloc] = v; next[alloc] = head[u]; head[u] = alloc++;
	end_point[alloc] = u; next[alloc] = head[v]; head[v] = alloc++;
}

void print()
{
	for (int i = 0; i < tot; i++)
		std::printf("%d %d %d\n", ans[i].x + 1, ans[i].y + 1, ans[i].z + 1);
}

void add_ans(int x, int y, int z)
{
	ans[tot].x = x; ans[tot].y = y; ans[tot++].z = z;
}

int dfs(int u)
{
	vis[u] = true; time[u] = tick++;
	int remain_edge = -1;
	for (int p = head[u]; p; p = next[p]) {
		int v = end_point[p];
		if (!vis[v]) {
			int r = dfs(v);
			if (r == -1) {
				if (remain_edge == -1)
					remain_edge = v;
				else {
					add_ans(remain_edge, u, v);
					remain_edge = -1;
				}
			} else
				add_ans(u, v, r);
		} else
		if (vis[v] && time[v] > time[u]) {
			if (remain_edge == -1)
				remain_edge = v;
			else {
				add_ans(remain_edge, u, v);
				remain_edge = -1;
			}
		}

	}
	return remain_edge;
}

int main()
{
	std::scanf("%d %d", &n, &m);
	for (int i = 0, x, y; i < m; i++) {
		std::scanf("%d %d", &x, &y);
		add_edge(--x, --y);
	}

	if (dfs(0) != -1 || tot * 2 != m)
		std::printf("NO\n");
	else
		print();
}

